/**
 * @file Defaine the wechat message handle functions here
 * 
 * @author Steve Gao<gaofu@parllay.com>
 */
const async                 = require('async');
const parseXML              = require('xml2js').parseString;
const redisKey              = require('../redisKey');
const utils                 = require('../utils');
const WeChatService         = require('./wechat-service').WeChatService;
const WeChatMessageHelper   = require('../helpers/wechat-helpers').WeChatMessageHelper;
const WeChatAPI             = require('../apis/wechat-api').WeChatAPI;

const {
        SystemServices,
        WeChatMessageTypes, 
        WeChatEventTypes,
        WeChatCSMessageTypes,
        DefaultWeChatReplyMessages,
        WeChatTestConstants
    } = require('../constants');

/**
 * The WeChat message service class
 * 
 * @class WeChatMessageService
 * @extends {BaseService}
 */
class WeChatMessageService extends WeChatService {

    /**
     * Handle the third-party application authentication event
     * 
     * @name handleWeChatAuthenticationEvent
     * @param {Object} params - The params come from WeChat
     * @param {Object} data - The data WeChat post to our server
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    handleWeChatAuthenticationEvent(params, data, parentCallback) {
        var me = this;
        async.waterfall([
            (callback) => {
                parseXML(data, {explicitArray: false}, callback);
            },
            (message, callback) => {
                if(message) {
                    var decryptedMessage = utils.decryptMessage(me.Context, params, message.xml);
                    callback(null, decryptedMessage);
                } else {
                    parentCallback(DefaultWeChatReplyMessages.SUCCESS);
                }
            }, (message, callback) => {
                parseXML(message, {explicitArray: false}, callback);
            }, (message, callback) => {
                if(message) {
                    switch(message.xml.InfoType) {
                        case 'component_verify_ticket':
                            me.refreshPlatformAccessToken(message.xml.AppId, message.xml.ComponentVerifyTicket, parentCallback);
                            break;
                        default:
                            parentCallback(DefaultWeChatReplyMessages.SUCCESS);
                    }
                } else {
                    parentCallback(DefaultWeChatReplyMessages.SUCCESS);
                }
            }
        ]);
    }

    /**
     * Handle the message come from WeChat
     * 
     * @name handleWeChatUserMessage
     * @param {Object} params - The params come from WeChat
     * @param {Object} data - The data come from WeChat
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    handleWeChatUserMessage(params, data, parentCallback){
        var me = this;
        async.waterfall([
            function(callback) {
                parseXML(data, {explicitArray: false}, callback);
            },
            function(message, callback) {
                if(message) {
                    var decryptedMessage = utils.decryptMessage(me.Context, params, message.xml);
                    callback(null, decryptedMessage);
                } else {
                    parentCallback(DefaultWeChatReplyMessages.SUCCESS);
                }
            }, function(message, callback) {
                parseXML(message, {explicitArray: false}, callback);
            }, function(message, callback) {
                if(message) {
                    if (!message.xml) {
                        return parentCallback(DefaultWeChatReplyMessages.SUCCESS);
                    }

                    var fromUser    = message.xml.ToUserName;
                    var toUser      = message.xml.FromUserName;
                    var appId           = params.app_id;
                    var testAppId       = me.WeChatConfig.test_app_id;
                    var testUserName    = me.WeChatConfig.test_user_name;
                    var msgType         = message.xml.MsgType;
                    
                    // If the appId is the WeChat test automation appId, we will do some special logic here
                    if (appId === testAppId) { 
                        me.handleWeChatTestMessage(fromUser, toUser, msgType, message.xml, params, (content) => {
                            parentCallback(content);
                        });
                    } else {
                        me.handleWeChatBillboardMessage(fromUser, toUser, msgType, message.xml, params, (content) => {
                            parentCallback(content);
                        });
                    }
                    
                } else {
                    parentCallback(DefaultWeChatReplyMessages.SUCCESS);
                }
            }
        ]);
    }
    
    /**
     * Handler the test cases from WeChat automation message testing
     * 
     * @name handleWeChatTestMessage
     * @param {String} fromUser - The test user name
     * @param {String} toUser - The to user
     * @param {String} msgType - The message type
     * @param {Object} message - The message object
     * @param {Object} params - The parameters to handle the message
     * @returns
     * 
     * @memberOf WeChatMessageService
     */
    handleWeChatTestMessage(fromUser, toUser, msgType, message, params, parentCallback) {
        switch(msgType) {
        case WeChatMessageTypes.TEXT: {
            var text = message.Content;
            if (text === WeChatTestConstants.TESTCOMPONENT_MSG_TYPE_TEXT) {
                // If the text message is TESTCOMPONENT_MSG_TYPE_TEXT, 
                //then response TESTCOMPONENT_MSG_TYPE_TEXT_from_callback
                let textContent = WeChatTestConstants.TESTCOMPONENT_MSG_TYPE_TEXT_CALLBACK;
                console.log(`Test text message: ${text}!`);
                let encryptedMessage = this.getEncryptedTextMessage(fromUser, toUser, textContent, params);
                parentCallback(encryptedMessage);
            } else if (text.startsWith(WeChatTestConstants.QUERY_AUTH_CODE)) {
                // If the message is text and the content is QUERY_AUTH_CODE:$query_auth_code$, 
                // will return empty immediately and then send the $query_auth_code$ via the WeChat 
                // customer service API to the test user
                parentCallback(DefaultWeChatReplyMessages.EMPTY);
                var queryAuthCode = text.substring(WeChatTestConstants.QUERY_AUTH_CODE.length);
                // Send the customer service message to the test user
                this.sendTestCustomerServiceMessage(queryAuthCode, toUser);
            } else {
                parentCallback(DefaultWeChatReplyMessages.SUCCESS);
            }
        break;
        }
        case WeChatMessageTypes.EVENT:{
            // If the message type is event, will response the text message 
            // with content message.Event + "from_callback"
            let textContent = `${message.Event}${WeChatTestConstants.FROM_CALLBACK}`;

            console.log(`Test event message: ${message.Event}`);

            this.sendCustomerServiceMessage(this.TestAccessToken, toUser, WeChatCSMessageTypes.TEXT, textContent, (data) => {
                parentCallback(DefaultWeChatReplyMessages.SUCCESS);
            });
            /*let encryptedMessage = this.getEncryptedTextMessage(
                fromUser, toUser, textContent, params);
            
            parentCallback(encryptedMessage);*/
        break;
        }
        default:
            parentCallback('success');
        }
    }

    /**
     * Get encrypted reply message
     * 
     * @name getEncryptedTextMessage
     * @param {String} fromUser - The open id of message sender
     * @param {any} toUserName - The open id of message recipient
     * 
     * @memberOf WeChatMessageService
     */
    
    getEncryptedTextMessage(fromUser, toUser, content, params) {
        let textMessage = WeChatMessageHelper.getReplyTextMessage(fromUser, toUser, content);
        console.log(`Test event plain message: ${textMessage}`);
        params.ToUserName = toUser;
        params.is_full = true;
        let encryptedContent = utils.encryptMessage(this.Context, params, textMessage);
        //let encryptedMessage = WeChatMessageHelper.getReplyEncryptMessage(toUser, encryptedContent);
        console.log(`Test event encrypted message: ${JSON.stringify(encryptedContent)}`);
        return encryptedContent;
    }

    /**
     * Get encrypted reply news message
     * 
     * @name getEncryptedNewsMessage
     * @param {String} fromUser - The open id of message sender
     * @param {String} toUser - The open id of message recipient
     
     * 
     * @memberOf WeChatMessageService
     */
    getEncryptedNewsMessage(fromUser, toUser, articles, params) {
        let newsMessage = WeChatMessageHelper.getReplyNewsMessage(fromUser, toUser, articles);
        console.log(`News Message: ${newsMessage}`);

        params.ToUserName = toUser;
        params.is_full = true;
        let encryptedNewsContent = utils.encryptMessage(this.Context, params, newsMessage);
        console.log(encryptedNewsContent);

        return encryptedNewsContent;
    }

    get TestAccessToken() {
        return this._TestAccessToken;
    }

    /**
     * Send customer service test message
     * 
     * @name sendTestCustomerServiceMessage
     * @param {String} authCode - The pre_auth_code
     * @param {String} toUser - The recipient's openId
     * 
     * @returns {vodi}
     */
    sendTestCustomerServiceMessage(authCode, toUser) {
        var me = this;
        this.acquireStorageClients({
            redis: true
        }, function(clients) {
            var redis = clients.redis;
            var componentAppId = me.WeChatConfig.component_app_id;
            var platformKey = redisKey.platform_auth(componentAppId);
            async.waterfall([
                function(callback) {
                    redis.hgetall(platformKey, callback);
                }, function(authInfo, callback) {
                    me.releaseStorageClients(clients);
                    if (!authInfo) {
                        //TODO: Add log here
                        console.log('Can not get the platform authentication information');
                    } else {
                        me.getAuthorizerAccessToken(componentAppId, authInfo.token, authCode, (error, data) => {
                            
                            if(data && data.authorization_info) {
                                me._TestAccessToken = data.authorization_info.authorizer_access_token;
                                callback(null, data.authorization_info.authorizer_access_token);
                            } else {
                                //TODO: Add log here
                                console.log('Can not get access token');
                                callback(null, null);
                            }
                        });
                    }
                }, function(accessToken, callback) {
                    if (accessToken) {
                        var textContent = `${authCode}${WeChatTestConstants.FROM_API}`;
                        me.sendCustomerServiceMessage(accessToken, toUser, WeChatCSMessageTypes.TEXT, textContent, (result) => {
                            console.log(result);
                        });
                    }
                }
            ]);
        }, function(error) {
            //TODO: Add log here
            //Do nothing here
        });
    }
    
    /**
     * Send customer service message to user
     * 
     * @name sendCustomerServiceMessage
     * @param {String} accessToken - The official account's access token
     * @param {String} toUser - The recipient's openId
     * @param {String} msgType - The customer service message type
     * @param {String} content - The message content
     * @param {Function} parentCallback - The callback function
     * 
     * @return {void}
     */
    sendCustomerServiceMessage(accessToken, toUser, msgType, content, parentCallback) {
        
        var message = null;
        switch(msgType) {
        case WeChatCSMessageTypes.TEXT:
            message = WeChatMessageHelper.getCustomerTextMessage(toUser, content);
            break;
        case WeChatCSMessageTypes.NEWS:
            message = WeChatMessageHelper.getCustomerNewsMessage(toUser, content);
            break;
        default:
            return parentCallback('Not supported message type');
        }

        WeChatAPI.sendCustomerServiceMessage(accessToken, message, function(error, data) {
            parentCallback(data);
        });
    }

    /**
     * Send customer service message to user
     * 
     * @name sendCustomerServiceMessage
     * @param {String} appId - The official account's appId
     * @param {String} toUser - The recipient's openId
     * @param {String} msgType - The customer service message type
     * @param {String} content - The message content
     * @param {Function} parentCallback - The callback function
     * 
     * @return {void}
     */
    sendCustomerServiceMessageByAppId(appId, toUser, msgType, content, parentCallback) {
        var me = this;
        this.acquireStorageClients({
            redis: true
        }, (clients) => {
            var redis = clients.redis;

            async.waterfall([
                (callback) => {
                    var clientAuthKey = redisKey.client_auth(appId);
                    redis.hgetall(clientAuthKey, callback);
                }, (authInfo, callback) => {
                    if (!authInfo) {
                        parentCallback(DefaultWeChatReplyMessages.SUCCESS);
                    } else {
                        callback(null, authInfo.accessToken);
                    }
                }, (accessToken, callback) => {
                    me.releaseStorageClients(clients);
                    me.sendCustomerServiceMessage(accessToken, toUser, msgType, content, parentCallback)
                }
            ])
        }, (error) => {
            parentCallback(error);
        });
    }

    /**
     * Handle the normal WeChat message for WeChat user
     * 
     * @name handleWeChatBillboardMessage
     * @param {String} fromUser - The official account's id
     * @param {String} toUser - The recipient's openId 
     * @param {String} msgType - The message type
     * @param {Object} message - The parsed message XML object
     * @param {Object} params - The parameters passed by WeChat
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    handleWeChatBillboardMessage(fromUser, toUser, msgType, message, params, parentCallback) {
        switch(msgType) {
        case WeChatMessageTypes.EVENT:
        this.handleWeChatBillboardMessageEvent(fromUser, toUser, message, params, parentCallback);
        break;
        case WeChatMessageTypes.TEXT:
            var links = utils.getLinks(message.Content || '');
            if (links && links.length > 0) {
                this.handleWeChatMessageWithLinks(fromUser, toUser, links, null, params, parentCallback);
            } else {
                parentCallback(DefaultWeChatReplyMessages.SUCCESS);
            }
            break;
        case WeChatMessageTypes.LINK:
            this.handleWeChatMessageWithLinks(fromUser, toUser, [message.Url], message, params, parentCallback);
            break;
        default:
            return parentCallback(DefaultWeChatReplyMessages.SUCCESS);
        }
    }

    /**
     * Handle the wechat message with links
     * 
     * @name handleWechatMessageWithLinks
     * @param {Array} links - The array of links
     * @param {Object} data - The extra data
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    handleWeChatMessageWithLinks(fromUser, toUser, links, data, params, parentCallback) {
        var me = this;
        this.wallService.createTrackableLinkForWeChatUser(toUser, links[0], data, (articles) => {
            if (articles.length > 0) {
                var message = me.getEncryptedNewsMessage(fromUser, toUser, articles, params);
                parentCallback(message);
            } else {
                parentCallback(DefaultWeChatReplyMessages.SUCCESS);
            }
        });
        
    }

    /**
     * Handle the wechat event message
     * 
     * @name handleWeChatBillboardMessageEvent
     * @param {String} fromUser - The event sender
     * @param {String} toUser - The event receiver
     * @param {String} message - The wechat message
     * @param {Object} params - The parameters 
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    handleWeChatBillboardMessageEvent(fromUser, toUser, message, params, parentCallback) {
        var me = this;
        var wallId = null;
        var userId = null;
        switch(message.Event) {
        case WeChatEventTypes.SCAN:
            var key = message.EventKey;
            if (key.startsWith('wall:')) {
                wallId = key.substring(5);
            }

            if (key.startsWith('nuser:')) {
                userId = key.substring(6);
            }
        break;
        case WeChatEventTypes.SUBSCRIBE:
            if (message.EventKey && message.EventKey.startsWith("qrscene_")) {
                var key = message.EventKey.substring(8);
                if (key.startsWith('wall:')) {
                    wallId = key.substring(5);
                }

                if (key.startsWith('nuser:')) {
                    userId = key.substring(6);
                }
            } else {
                parentCallback(DefaultWeChatReplyMessages.SUCCESS);
            }
        break;
        default:
            break;
        }

        if (wallId && params.app_id === this.WeChatConfig.managed_app_id) {
            
            this.wallService.getTrialWallInfoByOpenId(params.app_id, toUser, wallId, function(wall) {
                let url = `http://${params.host}/wall/${wallId}`;
                let encryptedMessage = me.getEncryptedTextMessage(fromUser, toUser, `您好, 这是我们为您准备的试用版的朋友圈，请点击链接体验[${url}]。`, params);
                parentCallback(encryptedMessage);
                /*if (wall.name) {
                    var description = null;
                    var url = `http://${params.host}/wall/${wallId}`;
                    var content = null;
                    var messageType = null;
                    if (wall.photo) {
                        description = `您好${wall.user_name}, 这是我们为您准备的试用版的朋友圈，请点击体验。`;
                        messageType = WeChatCSMessageTypes.TEXT;
                        content = [
                                {
                                    "title" : `${wall.name}跨屏朋友圈试用版`,
                                    "description" : description,
                                    "url" : url,
                                    "picurl" : wall.photo
                                }
                        ];
                    } else {
                        description = `您好${wall.user_name}, 这是我们为您准备的试用版的朋友圈，请点击链接体验[${url}]。`
                        messageType = WeChatCSMessageTypes.NEWS;

                        content = description;
                    }

                    let encryptedMessage = 

                    me.sendCustomerServiceMessageByAppId(params.appId, toUser, messageType, content, (data) => {
                        console.log(data);
                        parentCallback(DefaultWeChatReplyMessages.SUCCESS);
                    });
                } else {
                    parentCallback(DefaultWeChatReplyMessages.SUCCESS);
                }*/
            });
        } else if (userId && params.app_id === this.WeChatConfig.managed_app_id) {
            this.wallService.createWallForNewWeChatUser(userId, params.app_id, toUser, (result) => {
                if (result.success) {
                    let url = `http://${params.host}/wall/${result.data.id}`;

                    let encryptedMessage = me.getEncryptedTextMessage(fromUser, toUser, `${result.data.message}${url}`, params);
                    parentCallback(encryptedMessage);
                } else {
                    let encryptedMessage = me.getEncryptedTextMessage(fromUser, toUser, `${result.error}`);
                    parentCallback(encryptedMessage);
                }
                
            });
        } else {
            parentCallback(DefaultWeChatReplyMessages.SUCCESS);
        }
    }
    /**
     * Send customer service messsage to wall administrators
     * 
     * @name sendCustomerServiceMessageToWallAdministrators
     * @param {String} wallId - The wall id
     * @param {String} message - The message
     * 
     * @returns {void}
     */
    sendCustomerServiceMessageToWallAdministrators(wallId, messages) {
        utils.checkArrayParameter(messages, 'messages');

        //TODO: move it into configuration
        /*messages.push({
            "title" : "知识库和使用帮助",
            "description" : "知识库和使用帮助",
            "url" : "https://parllay.kf5.com/hc/kb/section/98884/",
            "picurl" : "http://cdn2.parllay.cn/images/billboard-cs-icon.jpg"
        });
        messages.push({
            "title" : "每个公众号都需要一个轻社区",
            "description" : "每一个公众号都需要一个轻社区",
            "url" : "http://v2.parllay.cn/",
            "picurl" : "http://files.parllay.cn/images/billboard-logo.png"
        });*/
        var me = this;
        async.waterfall([
            (callback) => {
                me.wallService.getWallById(wallId, (wall) => {
                    callback(null, wall);
                });
            },
            (wall, callback) => {
                var appId = null;
                if (wall.sources) {
                    var wechatSource = wall.sources.find((source) => {
                        return source.type === 'wechat';
                    });
                    appId = wechatSource && wechatSource.social_id;
                }

                if ( !appId ) {
                    appId = wall.app_id;
                }
                callback(null, appId);
            },
            (appId, callback) => {
                me.acquireStorageClients({
                    redis: true
                }, (clients) => {
                    var redis = clients.redis;
                    var clientAuthKey = redisKey.client_auth(appId);
                    redis.hgetall(clientAuthKey, (error, result) => {
                        me.releaseStorageClients(clients);
                        callback(null, result);
                    });
                }, (error) => {
                    me.logger.error(error);
                });
            },
            (authInfo, callback) => {
                if (authInfo && authInfo.token) {
                    callback(null, authInfo.token);
                }
            },
            (token, callback) => {
                me.wallService.getWallAdministrators(wallId, (administrators) => {
                    callback(null, token, administrators);
                });
            },
            (token, administrators) => {
                if (administrators && administrators.length > 0) {
                    var functionChain = [];
                    administrators.forEach((administrator) => {
                        if (!administrator.is_anonymouse) {
                            functionChain.push((callback) => {
                                me.sendCustomerServiceMessage(token, administrator.open_id, WeChatCSMessageTypes.NEWS, messages, (result) => {
                                    me.logger.info(`Message sent to ${administrator.open_id} with result`, result);
                                    callback(null);
                                });
                            });
                        }
                    });
                    functionChain.push(() => {
                        me.logger.info(`Message sent successfully to all wall administrators.`);
                    });
                    async.waterfall(functionChain);
                }
            }
        ])
    }
}

exports.WeChatMessageService = WeChatMessageService;